﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using RatingAuthor;

namespace ConditionTreeTestStand
{
  public abstract class Operator
  {
    public static IEnumerable<Operator> GetOperators(OperandType type)
    {
      switch (type)
      {
        case OperandType.Bool: return Bool;
        case OperandType.Int: return Int;
        case OperandType.Float: return Float;
        case OperandType.String: return String;
        case OperandType.Enum: return Enum;
      }
      throw new NotSupportedException();
    }

    #region ' Operators '

    private static readonly Operator[] Bool =
      new[]
        {
          new NAry<bool, bool>("И", "∧", x => x.All(p => p), ExpressionType.AndAlso),
          new NAry<bool, bool>("Или", "∨", x => x.Any(p =>p ), ExpressionType.OrElse),
        };

    private static readonly Operator[] Int =
      new[]
        {
          new Binary<int, bool>("Равно", "=", (x, y) => x == y),
          new Binary<int, bool>("Больше", ">", (x, y) => x > y),
          new Binary<int, bool>("Меньше", "<", (x, y) => x < y),
          new Binary<int, bool>("Не равно", "≠", (x, y) => x != y),
          new Binary<int, bool>("Больше или равно", "≥", (x, y) => x >= y),
          new Binary<int, bool>("Меньше или равно", "≤", (x, y) => x <= y),
        };

    private static readonly Operator[] Float =
      new[]
        {
          new Binary<double, bool>("Равно", "=", (x, y) => x == y),
          new Binary<double, bool>("Больше", ">", (x, y) => x > y),
          new Binary<double, bool>("Меньше", "<", (x, y) => x < y),
          new Binary<double, bool>("Не равно", "≠", (x, y) => x != y),
          new Binary<double, bool>("Больше или равно", "≥", (x, y) => x >= y),
          new Binary<double, bool>("Меньше или равно", "≤", (x, y) => x <= y),
        };

    private static readonly Operator[] String =
      new[]
        {
          new Binary<string, bool>("Равно", "=", (x, y) => string.Compare(x, y, true) == 0),
          new Binary<string, bool>("Не равно", "≠", (x, y) => string.Compare(x, y, true) != 0),
          new Binary<string, bool>("Начинается с", "↦", (x, y) => x.StartsWith(y, StringComparison.OrdinalIgnoreCase)),
          new Binary<string, bool>("Не начинается с", "?", (x, y) => !x.StartsWith(y, StringComparison.OrdinalIgnoreCase)),
          new Binary<string, bool>("Заканчивается на", "⇥", (x, y) => x.EndsWith(y, StringComparison.OrdinalIgnoreCase)),
          new Binary<string, bool>("Не заканчивается на", "?", (x, y) => !x.EndsWith(y, StringComparison.OrdinalIgnoreCase)),
          new Binary<string, bool>("Содержит", "∼", (x, y) => x.ToLower().Contains(y.ToLower())),
          new Binary<string, bool>("Не содержит", "≁", (x, y) => !x.ToLower().Contains(y.ToLower())),
        };
    

    private static readonly Operator[] Enum =
      new[]
        {
          new Binary<GenresSet, bool>("Равно", "=", (x, y) => x == y),
          new Binary<GenresSet, bool>("Не равно", "≠", (x, y) => x != y),
          new Binary<GenresSet, bool>("Не содержит ничего кроме", "⊃", (x, y) => (x & y) == x),
          new Binary<GenresSet, bool>("Содержит хотя бы одно из", "∪", (x, y) => (x & y) != 0),
          new Binary<GenresSet, bool>("Содержит все из", "⊂", (x, y) => (x & y) == y),
          new Binary<GenresSet, bool>("Не содержит ничего из", "?", (x, y) => (x & y) == 0),
        };

    #endregion
    
    public abstract Type Type { get; }
    public abstract OperandType OperandType { get; }
    public abstract OperatorArity Arity { get; }
    public abstract string Name { get; }
    public abstract string Symbol { get; }
    public abstract object Apply(IEnumerable<object> args);
    public object Apply(params object[] args)
    {
      return Apply((IEnumerable<object>) args);
    }

    public Expression GetExpression(IEnumerable<Expression> exp)
    {
      var expList = exp.ToList();
      if (expList.Count == 0) return null;
      if (expList.Count == 1) return expList[0];
      return expList.Skip(1).Aggregate(expList[0],
        (current, next) => MakeBinary(Exp, current, next));
    }

    #region ' Implementation '

    protected abstract Expression Exp { get; }

    #region Nested type: Binary

    private class Binary<TArg, TResult> : Operator
    {
      private readonly Func<TArg, TArg, TResult> _func;
      private readonly string _name;
      private readonly OperandType _operandType;
      private readonly Type _type;
      private readonly string _symbol;
      private readonly Expression _exp;

      internal Binary(string name, string symbol, Expression<Func<TArg, TArg, TResult>> func)
      {
        _name = name;
        _symbol = symbol;
        _func = func.Compile();
        _type = typeof (TArg);
        _operandType =  _type.ToOperandType();
        _exp = func.Body;
      }

      public override Type Type
      {
        get { return _type; }
      }

      public override OperandType OperandType
      {
        get { return _operandType; }
      }

      public override OperatorArity Arity
      {
        get { return OperatorArity.Two; }
      }

      public override string Name
      {
        get { return _name; }
      }

      public override string Symbol
      {
        get { return _symbol; }
      }

      public override object Apply(IEnumerable<object> args)
      {
        return _func((TArg) args.ElementAt(0), (TArg) args.ElementAt(1));
      }

      protected override Expression Exp
      {
        get { return _exp; }
      }
    }

    #endregion

    #region Nested type: NAry

    private class NAry<TArg, TResult> : Operator
    {

      protected override Expression Exp
      {
        get { return _exp; }
      }
      private readonly Func<IEnumerable<TArg>, TResult> _func;
      private readonly Expression _exp;
      private readonly string _name;
      private readonly string _symbol = "∧∪∨∩⊂⊃⊅⊄≠=≠=><≤≥";
      private readonly OperandType _operandType;
      private readonly Type _type;
      public NAry(string name, string symbol, Func<IEnumerable<TArg>, TResult> func, ExpressionType expressionType)
      {
        _name = name;
        _symbol = symbol;
        _func = func;
        _exp = Expression.MakeBinary(expressionType, 
                                     Expression.Parameter(typeof(bool), "x"),
                                     Expression.Parameter(typeof(bool), "y"));
        _type = typeof (TArg);
        _operandType = _type.ToOperandType();
      }

      public override Type Type
      {
        get { return _type; }
      }

      public override OperandType OperandType
      {
        get { return _operandType; }
      }

      public override OperatorArity Arity
      {
        get { return OperatorArity.N; }
      }

      public override string Name
      {
        get { return _name; }
      }
      public override string Symbol
      {
        get { return _symbol; }
      }

      public override object Apply(IEnumerable<object> args)
      {
        return _func(args.Cast<TArg>());
      }
    }

    #endregion

    private static Expression MakeBinary(Expression samplExp, Expression newLeft, Expression newRight)
    {
      var binaryExp = samplExp as BinaryExpression;
      if (binaryExp != null)
      {
        var l = Replace(binaryExp.Left, newLeft, newRight);
        var r = Replace(binaryExp.Right, newLeft, newRight);
        return Expression.MakeBinary(samplExp.NodeType, l, r);
      }
      var callExp = samplExp as MethodCallExpression;
      if (callExp != null)
      {
        var obj = Replace(callExp.Object, newLeft, newRight);
        var args = callExp.Arguments.Select(arg => Replace(arg, newLeft, newRight))/*.ToArray()*/;
        return Expression.Call(obj, callExp.Method, args);
      }
      throw new NotImplementedException();
    }
    private static Expression Replace(Expression exp, Expression newLeft, Expression newRight)
    {
      if (IsX(exp)) return newLeft;
      if (IsY(exp)) return newRight;
      if (exp is BinaryExpression) return MakeBinary(exp, newLeft, newRight);
      if (exp is MethodCallExpression) return MakeBinary(exp, newLeft, newRight);
      return exp;
    }
    private static bool IsX(Expression expression)
    {
      var unaryExpression = expression as UnaryExpression;
      if (unaryExpression != null) return IsX(unaryExpression.Operand);

      var parameterExpression = expression as ParameterExpression;
      return parameterExpression != null && parameterExpression.Name == "x";
    }
    private static bool IsY(Expression expression)
    {
      var unaryExpression = expression as UnaryExpression;
      if (unaryExpression != null) return IsY(unaryExpression.Operand);
//      var methodCallExp = expression as MethodCallExpression;
//      if (methodCallExp != null) return methodCallExp.Arguments.Any(IsY);

      var parameterExpression = expression as ParameterExpression;
      return parameterExpression != null && parameterExpression.Name == "y";
    }

    public override string ToString()
    {
      return Symbol;
    }
  }

  #endregion
}